<!DOCTYPE html>
<html lang="zh_cn">
<head>
          <title>来玩魔王的咚</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charset="utf-8" />
        <!-- twitter card metadata -->
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="/images/mowang.png">
<meta name="twitter:site" content="">
<meta name="twitter:title" content="pytest（六）捕获日志及输出">
<meta name="twitter:description" content="<p>管理logging日志；捕获标准输出和标准错误。对日志和输出断言测试。</p>">
        <!-- OG Tags -->
<meta property="og:url" content="/pytest-capture-logging-and-output.html"/>
<meta property="og:title" content="来玩魔王的咚 | pytest（六）捕获日志及输出" />
<meta property="og:description" content="<p>管理logging日志；捕获标准输出和标准错误。对日志和输出断言测试。</p>" />
        <!-- favicon -->
        <link rel="icon" type="image/png" href="/images/mowang.png">
        <!-- moment.js for date formatting -->
        <script src="/theme/js/moment.js"></script>
        <!-- css -->
        <link rel="stylesheet" type="text/css" href="/theme/css/main.css" />
        <!-- 左边的menu，如果页面高度不够，就跟着滚动，否则文章分类显示不全 -->
        <link rel="stylesheet" type="text/css" href="/theme/css/mycss/menu.css" />
		<script>
			
                /*! grunt-grunticon Stylesheet Loader - v2.1.2 | https://github.com/filamentgroup/grunticon | (c) 2015 Scott Jehl, Filament Group, Inc. | MIT license. */
    
    (function(e){function t(t,n,r,o){"use strict";function a(){for(var e,n=0;u.length>n;n++)u[n].href&&u[n].href.indexOf(t)>-1&&(e=!0);e?i.media=r||"all":setTimeout(a)}var i=e.document.createElement("link"),l=n||e.document.getElementsByTagName("script")[0],u=e.document.styleSheets;return i.rel="stylesheet",i.href=t,i.media="only x",i.onload=o||null,l.parentNode.insertBefore(i,l),a(),i}var n=function(r,o){"use strict";if(r&&3===r.length){var a=e.navigator,i=e.Image,l=!(!document.createElementNS||!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect||!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1")||e.opera&&-1===a.userAgent.indexOf("Chrome")||-1!==a.userAgent.indexOf("Series40")),u=new i;u.onerror=function(){n.method="png",n.href=r[2],t(r[2])},u.onload=function(){var e=1===u.width&&1===u.height,a=r[e&&l?0:e?1:2];n.method=e&&l?"svg":e?"datapng":"png",n.href=a,t(a,null,null,o)},u.src="",document.documentElement.className+=" grunticon"}};n.loadCSS=t,e.grunticon=n})(this);(function(e,t){"use strict";var n=t.document,r="grunticon:",o=function(e){if(n.attachEvent?"complete"===n.readyState:"loading"!==n.readyState)e();else{var t=!1;n.addEventListener("readystatechange",function(){t||(t=!0,e())},!1)}},a=function(e){return t.document.querySelector('link[href$="'+e+'"]')},c=function(e){var t,n,o,a,c,i,u={};if(t=e.sheet,!t)return u;n=t.cssRules?t.cssRules:t.rules;for(var l=0;n.length>l;l++)o=n[l].cssText,a=r+n[l].selectorText,c=o.split(");")[0].match(/US\-ASCII\,([^"']+)/),c&&c[1]&&(i=decodeURIComponent(c[1]),u[a]=i);return u},i=function(e){var t,o,a;o="data-grunticon-embed";for(var c in e)if(a=c.slice(r.length),t=n.querySelectorAll(a+"["+o+"]"),t.length)for(var i=0;t.length>i;i++)t[i].innerHTML=e[c],t[i].style.backgroundImage="none",t[i].removeAttribute(o);return t},u=function(t){"svg"===e.method&&o(function(){i(c(a(e.href))),"function"==typeof t&&t()})};e.embedIcons=i,e.getCSS=a,e.getIcons=c,e.ready=o,e.svgLoadedCallback=u,e.embedSVG=u})(grunticon,this);
                
                grunticon(["/theme/css/icons.data.svg.css", "/theme/css/icons.data.png.css", "/theme/css/icons.fallback.css"]);
            </script>
        <noscript><link href="/theme/css/icons.fallback.css" rel="stylesheet"></noscript>
        <!-- menu toggle javascript -->
        <script type="text/javascript">
            document.addEventListener("DOMContentLoaded", initMenu);
            
            function initMenu(){
                var menu = document.getElementById("menu");
                var menulink = document.getElementById("menu-link");
                menulink.addEventListener("click", function toggleMenu(){
                        window.event.preventDefault();
                        menulink.classList.toggle('active');
                        menu.classList.toggle('active');              
                    });
            };
        </script>
        <!-- 不蒜子 -->
        <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

    <meta name="description" content="<p>管理logging日志；捕获标准输出和标准错误。对日志和输出断言测试。</p>" />

    <meta name="tags" content="测试" />
  <!-- 替换部分base的样式，看文章时，要再宽一点，右边有很多空间可以撑开 -->
  <link rel="stylesheet" type="text/css" href="/theme/css/mycss/article.css" />

</head>
<body>
    <div role="banner" id="masthead">
        <header>
            <a href="/"><img src="/images/mowang.png" alt="McManus Logo"></a>
                <h1>来玩魔王的咚@骑士救兵</h1>
            <a href="#menu" id="menu-link">more stuff</a>
            <nav id="menu">
                <ul>
                        <li><a href="/tags">tags</a></li>
                            <li><a href="/category/cloud.html">Cloud</a></li>
                            <li><a href="/category/docker.html">Docker</a></li>
                            <li><a href="/category/go.html">Go</a></li>
                            <li><a href="/category/linux.html">Linux</a></li>
                            <li class="active"><a href="/category/python.html">Python</a></li>
                            <li><a href="/category/xue-xi-bi-ji.html">学习笔记</a></li>
                            <li><a href="/category/yun-wei-zi-dong-hua.html">运维自动化</a></li>
                </ul>
            </nav>
        </header>
    </div>
        <div class="page" role="main">
  <div class="article" role="article">
    <article>
        <footer>
            <a name="top"></a>
            <p>
              <time datetime=" 2022-03-25 09:02:00+08:00">
                <script>document.write(moment('2022-03-25 09:02:00+08:00').format('LL'));</script>
              </time>
              ~
              <time datetime=" 2022-03-25 09:02:00+08:00">
                <script>document.write(moment('2022-03-25 19:00:00+08:00').format('LL'));</script>
              </time>
            </p>
        </footer>
        <header>
          <h2>
            pytest（六）捕获日志及输出
          </h2>
        </header>
      <div class="content">
         <div class="toc">
<ul>
<li><a href="#da-yin-ri-zhi-he-shu-chu">打印日志和输出</a><ul>
<li><a href="#shu-chu-de-san-ge-jie-duan">输出的三个阶段</a></li>
<li><a href="#xiu-gai-ri-zhi-ge-shi">修改日志格式</a></li>
<li><a href="#jin-yong-bu-huo-de-shu-chu">禁用捕获的输出</a></li>
</ul>
</li>
<li><a href="#caplog-gu-jian">caplog固件</a><ul>
<li><a href="#xiu-gai-ri-zhi-deng-ji">修改日志等级</a></li>
<li><a href="#zai-with-shang-xia-wen-zhong-ju-bu-she-zhi">在with上下文中局部设置</a></li>
</ul>
</li>
<li><a href="#duan-yan-ri-zhi-xiao-xi">断言日志消息</a><ul>
<li><a href="#zhu-tiao-ri-zhi-shi-li-duan-yan">逐条日志实例断言</a></li>
<li><a href="#dui-zheng-ge-ri-zhi-wen-ben-duan-yan">对整个日志文本断言</a></li>
<li><a href="#caplogrecord_tuples-he-caplogmessages">caplog.record_tuples 和 caplog.messages</a></li>
<li><a href="#qing-kong-ri-zhi">清空日志</a></li>
</ul>
</li>
<li><a href="#chu-li-qi-ta-jie-duan-de-ri-zhi">处理其他阶段的日志</a><ul>
<li><a href="#kua-jie-duan-huo-qu-bu-huo-de-ri-zhi">跨阶段获取捕获的日志</a></li>
</ul>
</li>
<li><a href="#ri-zhi-yan-se-ding-zhi">日志颜色定制</a></li>
<li><a href="#dui-stdoutstderrstdin-mo-ren-de-bu-huo-xing-wei">对 stdout/stderr/stdin 默认的捕获行为</a><ul>
<li><a href="#bu-huo-de-she-zhi">捕获的设置</a></li>
</ul>
</li>
<li><a href="#shi-yong-print-jin-xing-debug">使用print进行debug</a></li>
<li><a href="#duan-yan-bu-huo-de-shu-chu">断言捕获的输出</a></li>
</ul>
</div>
<h3 id="da-yin-ri-zhi-he-shu-chu"><a class="toclink" href="#da-yin-ri-zhi-he-shu-chu">打印日志和输出</a></h3>
<p>pytest会自动捕获标准输出、标准错误、以及日志消息（默认捕获WARNING级别及以上的日志），之后会在每个失败的测试报告中展示出来。成功的测试不会打印这些内容：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">logging</span>

<span class="k">def</span> <span class="nf">test_out</span><span class="p">():</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Output to stdout&#39;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Output to strerr&#39;</span><span class="p">,</span> <span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;INFO&#39;</span><span class="p">)</span>        <span class="c1"># info 日志默认不输出</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;WARNING&#39;</span><span class="p">)</span>  <span class="c1"># 默认从warning级别开始输出日志</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;error&#39;</span><span class="p">)</span>
    <span class="k">assert</span> <span class="mi">0</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="kn">import</span> <span class="nn">pytest</span>
    <span class="n">pytest</span><span class="o">.</span><span class="n">main</span><span class="p">([</span><span class="vm">__file__</span><span class="p">])</span>

<span class="sd">&#39;&#39;&#39;</span>
<span class="sd">-------------------- Captured stdout call --------------------</span>
<span class="sd">Output to stdout</span>
<span class="sd">-------------------- Captured stderr call --------------------</span>
<span class="sd">Output to strerr</span>
<span class="sd">-------------------- Captured log call ----------------------- </span>
<span class="sd">WARNING  root:test_out.py:15 Message WARNING</span>
<span class="sd">ERROR    root:test_out.py:16 Message error</span>
<span class="sd">==================== short test summary info ================= </span>
<span class="sd">FAILED tests/test_out.py::test_out - assert 0</span>
<span class="sd">==================== 1 failed in 0.07s ======================= </span>
<span class="sd">&#39;&#39;&#39;</span>
</code></pre></div>

<h4 id="shu-chu-de-san-ge-jie-duan"><a class="toclink" href="#shu-chu-de-san-ge-jie-duan">输出的三个阶段</a></h4>
<p>上面的示例只有测试函数的输出。一次测试分setup（测试前的准备）、call（测试函数）、teardown（测试后的清理），三个阶段。<br>
每个阶段都可以有输出，每个阶段、每种类型的的输出都是单独分块展示的：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">logging</span>
<span class="kn">import</span> <span class="nn">pytest</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">message</span><span class="p">():</span>
    <span class="c1"># setup stage</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">&#39;Message in </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;setup&#39;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;This is in setup&#39;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Error in setup&#39;</span><span class="p">,</span> <span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span>
    <span class="k">yield</span>
    <span class="c1"># teardown stage</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">&#39;Message in </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;teardown&#39;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;This is in teardown&#39;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Error in teardown&#39;</span><span class="p">,</span> <span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">test_out</span><span class="p">(</span><span class="n">message</span><span class="p">):</span>
    <span class="c1"># call stage</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">&#39;Message in </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;call&#39;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;This is in call&#39;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Error in call&#39;</span><span class="p">,</span> <span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span>
    <span class="k">assert</span> <span class="mi">0</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="n">pytest</span><span class="o">.</span><span class="n">main</span><span class="p">([</span><span class="vm">__file__</span><span class="p">])</span>

<span class="sd">&#39;&#39;&#39;</span>
<span class="sd">---------------------- Captured stdout setup ---------------------- </span>
<span class="sd">This is in setup</span>
<span class="sd">---------------------- Captured stderr setup ---------------------- </span>
<span class="sd">Error in setup</span>
<span class="sd">----------------------- Captured log setup ------------------------ </span>
<span class="sd">WARNING  root:test_out2.py:13 Message in setup</span>
<span class="sd">---------------------- Captured stdout call ----------------------- </span>
<span class="sd">This is in call</span>
<span class="sd">---------------------- Captured stderr call ----------------------- </span>
<span class="sd">Error in call</span>
<span class="sd">------------------------ Captured log call ------------------------ </span>
<span class="sd">WARNING  root:test_out2.py:22 Message in call</span>
<span class="sd">-------------------- Captured stdout teardown --------------------- </span>
<span class="sd">This is in teardown</span>
<span class="sd">-------------------- Captured stderr teardown --------------------- </span>
<span class="sd">Error in teardown</span>
<span class="sd">---------------------- Captured log teardown ---------------------- </span>
<span class="sd">WARNING  root:test_out2.py:17 Message in teardown</span>
<span class="sd">===================== short test summary info ===================== </span>
<span class="sd">FAILED tests\test_out2.py::test_out - assert 0 </span>
<span class="sd">======================== 1 failed in 0.06s ======================== </span>
<span class="sd">&#39;&#39;&#39;</span>
</code></pre></div>

<p>不仅仅是独立的展示，捕获也是独立的，会在捕获的部分展开。  </p>
<h4 id="xiu-gai-ri-zhi-ge-shi"><a class="toclink" href="#xiu-gai-ri-zhi-ge-shi">修改日志格式</a></h4>
<p>默认的日志格式有，等级、模块、行号、消息。可以遵循logging模块的日志格式设置来修改日志的格式。<br>
在上一个示例的基础上，通过命令行参数修改日志格式，添加日志记录时间：</p>
<div class="highlight"><pre><span></span><code><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="c1"># pytest.main([__file__])</span>
    <span class="n">pytest</span><span class="o">.</span><span class="n">main</span><span class="p">([</span>
        <span class="vm">__file__</span><span class="p">,</span> 
        <span class="s1">&#39;--log-format=</span><span class="si">%(asctime)s</span><span class="s1"> </span><span class="si">%(levelname).3s</span><span class="s1"> </span><span class="si">%(message)s</span><span class="s1">&#39;</span><span class="p">,</span> 
        <span class="s1">&#39;--log-date-format=%Y-%m-</span><span class="si">%d</span><span class="s1"> %H:%M:%S&#39;</span>
    <span class="p">])</span>

<span class="sd">&#39;&#39;&#39;</span>
<span class="sd">------------------------------ Captured log setup ------------------------------ </span>
<span class="sd">2022-03-25 09:34:18 WAR Message in setup</span>
<span class="sd">------------------------------ Captured log call ------------------------------- </span>
<span class="sd">2022-03-25 09:34:18 WAR Message in call</span>
<span class="sd">---------------------------- Captured log teardown ----------------------------- </span>
<span class="sd">2022-03-25 09:34:18 WAR Message in teardown</span>
<span class="sd">=========================== short test summary info ============================ </span>
<span class="sd">FAILED tests\test_out2.py::test_out - assert 0</span>
<span class="sd">============================== 1 failed in 0.08s =============================== </span>
<span class="sd">&#39;&#39;&#39;</span>
</code></pre></div>

<p><strong>配置文件设置日志格式</strong><br>
同样的效果可以在设置在配置文件<code>pytest.ini</code>中：</p>
<div class="highlight"><pre><span></span><code><span class="k">[pytest]</span><span class="w"></span>
<span class="na">log_format</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">%(asctime)s %(levelname).3s %(message)s</span><span class="w"></span>
<span class="na">log_date_format</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">%Y-%m-%d %H:%M:%S</span><span class="w"></span>
</code></pre></div>

<h4 id="jin-yong-bu-huo-de-shu-chu"><a class="toclink" href="#jin-yong-bu-huo-de-shu-chu">禁用捕获的输出</a></h4>
<p>使用下面的命令行参数，会禁用对标准输出、标准错误以及日志消息捕获的输出：</p>
<div class="highlight"><pre><span></span><code>pytest --show-capture<span class="o">=</span>no
</code></pre></div>

<h3 id="caplog-gu-jian"><a class="toclink" href="#caplog-gu-jian">caplog固件</a></h3>
<p>cap是capture的缩写，是捕获，log是日志。caplog就是捕获日志。caplog固件，实现了对捕获日志相关的操作。  </p>
<h4 id="xiu-gai-ri-zhi-deng-ji"><a class="toclink" href="#xiu-gai-ri-zhi-deng-ji">修改日志等级</a></h4>
<p>默认捕获的是WARNING级别及以上的日志，可以修改日志捕获的等级：</p>
<div class="highlight"><pre><span></span><code><span class="n">caplog</span><span class="o">.</span><span class="n">set_level</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span>
</code></pre></div>

<p>下面使用的示例：</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">test_foo</span><span class="p">(</span><span class="n">caplog</span><span class="p">):</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;INFO&#39;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>  <span class="c1"># 这条log不捕获</span>
    <span class="n">caplog</span><span class="o">.</span><span class="n">set_level</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span>            <span class="c1"># 修改日志等级</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;INFO&#39;</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>  <span class="c1"># 这条log会被捕获</span>
    <span class="k">assert</span> <span class="mi">0</span>
</code></pre></div>

<p>默认修改的是root logger的日志设置，可以指定要设置的logger：</p>
<div class="highlight"><pre><span></span><code><span class="n">caplog</span><span class="o">.</span><span class="n">set_level</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">CRITICAL</span><span class="p">,</span> <span class="n">logger</span><span class="o">=</span><span class="s2">&quot;root.baz&quot;</span><span class="p">)</span>
</code></pre></div>

<p>这个的示例放在下面。  </p>
<h4 id="zai-with-shang-xia-wen-zhong-ju-bu-she-zhi"><a class="toclink" href="#zai-with-shang-xia-wen-zhong-ju-bu-she-zhi">在with上下文中局部设置</a></h4>
<p>可以使用with上下文在局部进行设置，并且同样支持logger参数：</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">test_bar</span><span class="p">(</span><span class="n">caplog</span><span class="p">):</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;INFO&#39;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="k">with</span> <span class="n">caplog</span><span class="o">.</span><span class="n">at_level</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">):</span>
        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;INFO&#39;</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>  <span class="c1"># 捕获打印</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;INFO&#39;</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>

    <span class="k">with</span> <span class="n">caplog</span><span class="o">.</span><span class="n">at_level</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">CRITICAL</span><span class="p">,</span> <span class="n">logger</span><span class="o">=</span><span class="s2">&quot;root.baz&quot;</span><span class="p">):</span>
        <span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="s2">&quot;root.baz&quot;</span><span class="p">)</span>           <span class="c1"># 创建logger</span>
        <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;ERROR&#39;</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>
        <span class="n">logger</span><span class="o">.</span><span class="n">critical</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;CRITICAL&#39;</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>  <span class="c1"># 捕获打印</span>

    <span class="k">assert</span> <span class="mi">0</span>
</code></pre></div>

<h3 id="duan-yan-ri-zhi-xiao-xi"><a class="toclink" href="#duan-yan-ri-zhi-xiao-xi">断言日志消息</a></h3>
<p>测试期间生成的日志会在两个层面进行记录。一个是记录每一条日志的<code>logging.LogRecord</code>实例，包含的属性比较全，每条日志一个实例。另一个是完整的日志文本。  </p>
<h4 id="zhu-tiao-ri-zhi-shi-li-duan-yan"><a class="toclink" href="#zhu-tiao-ri-zhi-shi-li-duan-yan">逐条日志实例断言</a></h4>
<p>在<code>caplog.records</code>中，包含了每一条日志的信息，可以逐条进行遍历：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">logging</span>

<span class="k">def</span> <span class="nf">do_something</span><span class="p">():</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;WARNING&#39;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;ERROR&#39;</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">test_baz</span><span class="p">(</span><span class="n">caplog</span><span class="p">):</span>
    <span class="n">do_something</span><span class="p">()</span>
    <span class="k">for</span> <span class="n">record</span> <span class="ow">in</span> <span class="n">caplog</span><span class="o">.</span><span class="n">records</span><span class="p">:</span>
        <span class="k">assert</span> <span class="n">record</span><span class="o">.</span><span class="n">levelname</span> <span class="o">!=</span> <span class="s2">&quot;CRITICAL&quot;</span>
        <span class="k">assert</span> <span class="n">record</span><span class="o">.</span><span class="n">msg</span> <span class="o">==</span> <span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span>
        <span class="k">assert</span> <span class="s1">&#39;Message&#39;</span> <span class="ow">in</span> <span class="n">record</span><span class="o">.</span><span class="n">message</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="kn">import</span> <span class="nn">pytest</span>
    <span class="n">pytest</span><span class="o">.</span><span class="n">main</span><span class="p">([</span><span class="vm">__file__</span><span class="p">])</span>
</code></pre></div>

<p><code>caplog.records</code>是一个列表，内部的元素是<code>'logging.LogRecord'</code>，包含了丰富的属性：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">logging</span>

<span class="k">def</span> <span class="nf">test_baz</span><span class="p">(</span><span class="n">caplog</span><span class="p">):</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;WARNING&#39;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">records</span><span class="p">))</span>
    <span class="nb">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">records</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">records</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="vm">__dict__</span><span class="p">)</span>
    <span class="k">assert</span> <span class="mi">0</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="kn">import</span> <span class="nn">pytest</span>
    <span class="n">pytest</span><span class="o">.</span><span class="n">main</span><span class="p">([</span><span class="vm">__file__</span><span class="p">])</span>

<span class="sd">&#39;&#39;&#39;</span>
<span class="sd">-------------------- Captured stdout call -------------------- </span>
<span class="sd">&lt;class &#39;list&#39;&gt;</span>
<span class="sd">&lt;class &#39;logging.LogRecord&#39;&gt;</span>
<span class="sd">name: root</span>
<span class="sd">msg: Message %s</span>
<span class="sd">args: (&#39;WARNING&#39;,)</span>
<span class="sd">levelname: WARNING</span>
<span class="sd">levelno: 30</span>
<span class="sd">pathname: i:\gitee.com\steeed\pytest-examples\tests\test_assert_msg2.py</span>
<span class="sd">filename: test_assert_msg2.py</span>
<span class="sd">module: test_assert_msg2</span>
<span class="sd">exc_info: None</span>
<span class="sd">exc_text: None</span>
<span class="sd">stack_info: None</span>
<span class="sd">lineno: 10</span>
<span class="sd">funcName: test_baz</span>
<span class="sd">created: 1648174272.1991668</span>
<span class="sd">msecs: 199.16677474975586</span>
<span class="sd">relativeCreated: 197.00121879577637</span>
<span class="sd">thread: 146412</span>
<span class="sd">threadName: MainThread</span>
<span class="sd">processName: MainProcess</span>
<span class="sd">process: 150344</span>
<span class="sd">message: Message WARNING</span>
<span class="sd">-------------------- Captured log call -------------------- </span>
<span class="sd">&#39;&#39;&#39;</span>
</code></pre></div>

<h4 id="dui-zheng-ge-ri-zhi-wen-ben-duan-yan"><a class="toclink" href="#dui-zheng-ge-ri-zhi-wen-ben-duan-yan">对整个日志文本断言</a></h4>
<p>这个一个在整段日志中查看关键字的例子：</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">do_something</span><span class="p">():</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;WARNING&#39;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;ERROR&#39;</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">test_baz</span><span class="p">(</span><span class="n">caplog</span><span class="p">):</span>
    <span class="n">do_something</span><span class="p">()</span>
    <span class="k">assert</span> <span class="s2">&quot;fail&quot;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">caplog</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</code></pre></div>

<p>这里考虑到了大小写的问题，但是也可能匹配到包含fail的其他单词。作为示例可以了，真正使用的时候，根据情况可以考虑使用更准确的匹配方式。  </p>
<p><code>caplog.text</code>是一整段的字符串，正如名称text那样，每条日志都逐行记录在其中：</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">test_baz</span><span class="p">(</span><span class="n">caplog</span><span class="p">):</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;WARNING&#39;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;ERROR&#39;</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">text</span><span class="p">))</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">text</span><span class="o">.</span><span class="fm">__repr__</span><span class="p">())</span>
    <span class="k">assert</span> <span class="mi">0</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="kn">import</span> <span class="nn">pytest</span>
    <span class="n">pytest</span><span class="o">.</span><span class="n">main</span><span class="p">([</span><span class="vm">__file__</span><span class="p">])</span>

<span class="sd">&#39;&#39;&#39;</span>
<span class="sd">------------------------------------------------ Captured stdout call ------------------------------------------------ </span>
<span class="sd">&lt;class &#39;str&#39;&gt;</span>
<span class="sd">&#39;WARNING  root:test_assert_msg3.py:14 Message WARNING 1\nERROR    root:test_assert_msg3.py:15 Message ERROR 2\n&#39;       </span>
<span class="sd">------------------------------------------------- Captured log call -------------------------------------------------- </span>
<span class="sd">&#39;&#39;&#39;</span>
</code></pre></div>

<h4 id="caplogrecord_tuples-he-caplogmessages"><a class="toclink" href="#caplogrecord_tuples-he-caplogmessages">caplog.record_tuples 和 caplog.messages</a></h4>
<p><code>caplog.record_tuples</code>是一个包含logger name、日志等级、日志消息，这3个字段的元组的列表，每条日志的以上3个属性的元素是列表的一个元素：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">logging</span>

<span class="k">def</span> <span class="nf">test_baz</span><span class="p">(</span><span class="n">caplog</span><span class="p">):</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;WARNING&#39;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;ERROR&#39;</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">record_tuples</span><span class="p">))</span>
    <span class="nb">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">record_tuples</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">record_tuples</span><span class="p">)</span>
    <span class="k">assert</span> <span class="mi">0</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="kn">import</span> <span class="nn">pytest</span>
    <span class="n">pytest</span><span class="o">.</span><span class="n">main</span><span class="p">([</span><span class="vm">__file__</span><span class="p">])</span>

<span class="sd">&#39;&#39;&#39;</span>
<span class="sd">----------------------- Captured stdout call ----------------------- </span>
<span class="sd">&lt;class &#39;list&#39;&gt;</span>
<span class="sd">&lt;class &#39;tuple&#39;&gt;</span>
<span class="sd">[(&#39;root&#39;, 30, &#39;Message WARNING 1&#39;), (&#39;root&#39;, 40, &#39;Message ERROR 2&#39;)] </span>
<span class="sd">------------------------ Captured log call ------------------------- </span>
<span class="sd">&#39;&#39;&#39;</span>
</code></pre></div>

<p>用法和<code>caplog.records</code>类似，但是更简洁，仅包含最主要的几个属性。  </p>
<p>另外还有一个更简单的<code>caplog.messages</code>，仅是所有日志消息的字符串组成的列表。  </p>
<h4 id="qing-kong-ri-zhi"><a class="toclink" href="#qing-kong-ri-zhi">清空日志</a></h4>
<p>整个测试阶段的日志都会追加到记录中。使用<code>caplog.clear()</code>方法可以清空当前捕获的所有日志，方便对后续生成的日志进行处理。<br>
可以在对当前生成的日志处理完成后，清理掉之前所有的日志记录，之后再捕获新的日志后，再进行处理：</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">test_baz</span><span class="p">(</span><span class="n">caplog</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">records</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">text</span><span class="p">))</span>    <span class="c1"># 0 0</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;WARNING&#39;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>  <span class="c1"># 记录了1条日志</span>
    <span class="nb">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">records</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">text</span><span class="p">))</span>    <span class="c1"># 1 49</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;ERROR&#39;</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>      <span class="c1"># 累积记录了2条日志</span>
    <span class="nb">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">records</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">text</span><span class="p">))</span>    <span class="c1"># 2 96</span>
    <span class="n">caplog</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>                                  <span class="c1"># 清空之前的日志</span>
    <span class="nb">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">records</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">text</span><span class="p">))</span>    <span class="c1"># 0 0</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;INFO&#39;</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>        <span class="c1"># INFO 等级的日志不捕获</span>
    <span class="nb">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">records</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">text</span><span class="p">))</span>    <span class="c1"># 0 0</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">&#39;Message </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;WARNING&#39;</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>  <span class="c1"># 累积记录了1条日志</span>
    <span class="nb">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">records</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">text</span><span class="p">))</span>    <span class="c1"># 1 49</span>
    <span class="k">assert</span> <span class="mi">0</span>
</code></pre></div>

<h3 id="chu-li-qi-ta-jie-duan-de-ri-zhi"><a class="toclink" href="#chu-li-qi-ta-jie-duan-de-ri-zhi">处理其他阶段的日志</a></h3>
<p>测试一共三个阶段：</p>
<ul>
<li>setup: 固件加载，固件函数返回之前的部分</li>
<li>call: 测试阶段</li>
<li>teardown: 固件销毁，固件函数yield之后的部分</li>
</ul>
<p>默认caplog固件中只有当前阶段的日志，可以在下面的示例中确认：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">logging</span>
<span class="kn">import</span> <span class="nn">pytest</span>

<span class="k">def</span> <span class="nf">do_something</span><span class="p">():</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">&#39;Do somethin </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;WARNING&#39;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">&#39;Do somthing </span><span class="si">%s</span><span class="s1"> </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;ERROR&#39;</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>

<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">message</span><span class="p">(</span><span class="n">caplog</span><span class="p">):</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">&#39;Message in </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;setup&#39;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">records</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">text</span><span class="p">))</span>  <span class="c1"># 捕获1条日志</span>
    <span class="k">yield</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">&#39;Message in </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;teardown&#39;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">records</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">text</span><span class="p">))</span>  <span class="c1"># 捕获1条日志</span>

<span class="k">def</span> <span class="nf">test_out</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">caplog</span><span class="p">):</span>
    <span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s1">&#39;Message in </span><span class="si">%s</span><span class="s1">&#39;</span><span class="p">,</span> <span class="s1">&#39;call&#39;</span><span class="p">)</span>
    <span class="n">do_something</span><span class="p">()</span>
    <span class="nb">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">records</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">caplog</span><span class="o">.</span><span class="n">text</span><span class="p">))</span>  <span class="c1"># 捕获3条日志</span>
    <span class="k">assert</span> <span class="mi">0</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="kn">import</span> <span class="nn">pytest</span>
    <span class="n">pytest</span><span class="o">.</span><span class="n">main</span><span class="p">([</span><span class="vm">__file__</span><span class="p">])</span>

<span class="sd">&#39;&#39;&#39;</span>
<span class="sd">--------------------- Captured stdout setup --------------------- </span>
<span class="sd">1 54</span>
<span class="sd">---------------------- Captured log setup ----------------------- </span>
<span class="sd">WARNING  root:test_caplog_when.py:17 Message in setup</span>
<span class="sd">--------------------- Captured stdout call ---------------------- </span>
<span class="sd">3 169</span>
<span class="sd">----------------------- Captured log call ----------------------- </span>
<span class="sd">WARNING  root:test_caplog_when.py:24 Message in call</span>
<span class="sd">WARNING  root:test_caplog_when.py:12 Do somethin WARNING 1        </span>
<span class="sd">ERROR    root:test_caplog_when.py:13 Do somthing ERROR 2</span>
<span class="sd">------------------- Captured stdout teardown -------------------- </span>
<span class="sd">1 57</span>
<span class="sd">--------------------- Captured log teardown --------------------- </span>
<span class="sd">WARNING  root:test_caplog_when.py:20 Message in teardown</span>
<span class="sd">==================== short test summary info ==================== </span>
<span class="sd">&#39;&#39;&#39;</span>
</code></pre></div>

<h4 id="kua-jie-duan-huo-qu-bu-huo-de-ri-zhi"><a class="toclink" href="#kua-jie-duan-huo-qu-bu-huo-de-ri-zhi">跨阶段获取捕获的日志</a></h4>
<p>使用<code>caplog.get_records(when)</code>可以跨阶段捕获日志，when是参数名，使用是替换成具体的阶段的名称。
每个阶段都可以获取到所有3个阶段的日志：</p>
<ul>
<li>3个阶段的名称分别是setup、call、teardown</li>
<li>返回的列表就是指定阶段的<code>caplog.records</code>获取到的对象</li>
<li>可以使用当前阶段的名称获取到当前阶段的<code>caplog.records</code>的对象，不过这同直接使用<code>caplog.records</code>是一样的，同样的id</li>
<li>3个阶段的<code>caplog.records</code>始终都存在，一开始是一个空列表。比如call阶段也可以获得teardown阶段的日志，因为teardown还未开始，一定是个空列表</li>
<li>跨阶段处理日志只有这个接口，caplog.text、caplog.record_tuples、caplog.messages这些都没有</li>
</ul>
<p>下面是一个在teardown阶段检查前两个阶段的日志的例子：</p>
<div class="highlight"><pre><span></span><code><span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
<span class="k">def</span> <span class="nf">window</span><span class="p">(</span><span class="n">caplog</span><span class="p">):</span>
    <span class="n">window</span> <span class="o">=</span> <span class="n">create_window</span><span class="p">()</span>
    <span class="k">yield</span> <span class="n">window</span>
    <span class="k">for</span> <span class="n">when</span> <span class="ow">in</span> <span class="p">(</span><span class="s2">&quot;setup&quot;</span><span class="p">,</span> <span class="s2">&quot;call&quot;</span><span class="p">):</span>
        <span class="n">messages</span> <span class="o">=</span> <span class="p">[</span>
            <span class="n">x</span><span class="o">.</span><span class="n">message</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">caplog</span><span class="o">.</span><span class="n">get_records</span><span class="p">(</span><span class="n">when</span><span class="p">)</span> <span class="k">if</span> <span class="n">x</span><span class="o">.</span><span class="n">levelno</span> <span class="o">==</span> <span class="n">logging</span><span class="o">.</span><span class="n">WARNING</span>
        <span class="p">]</span>
        <span class="k">if</span> <span class="n">messages</span><span class="p">:</span>
            <span class="n">pytest</span><span class="o">.</span><span class="n">fail</span><span class="p">(</span>
                <span class="s2">&quot;warning messages encountered during testing: </span><span class="si">{}</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">messages</span><span class="p">)</span>
            <span class="p">)</span>
</code></pre></div>

<h3 id="ri-zhi-yan-se-ding-zhi"><a class="toclink" href="#ri-zhi-yan-se-ding-zhi">日志颜色定制</a></h3>
<p>日志是由logging-plugin插件管理的，通过插件名称获取插件，修改插件的属性，可以修改日志的颜色：</p>
<div class="highlight"><pre><span></span><code><span class="nd">@pytest</span><span class="o">.</span><span class="n">hookimpl</span>
<span class="k">def</span> <span class="nf">pytest_configure</span><span class="p">(</span><span class="n">config</span><span class="p">):</span>
    <span class="n">logging_plugin</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">pluginmanager</span><span class="o">.</span><span class="n">get_plugin</span><span class="p">(</span><span class="s2">&quot;logging-plugin&quot;</span><span class="p">)</span>

    <span class="c1"># Change color on existing log level</span>
    <span class="n">logging_plugin</span><span class="o">.</span><span class="n">log_cli_handler</span><span class="o">.</span><span class="n">formatter</span><span class="o">.</span><span class="n">add_color_level</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">,</span> <span class="s2">&quot;cyan&quot;</span><span class="p">)</span>

    <span class="c1"># Add color to a custom log level (a custom log level `SPAM` is already set up)</span>
    <span class="n">logging_plugin</span><span class="o">.</span><span class="n">log_cli_handler</span><span class="o">.</span><span class="n">formatter</span><span class="o">.</span><span class="n">add_color_level</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">SPAM</span><span class="p">,</span> <span class="s2">&quot;blue&quot;</span><span class="p">)</span>
</code></pre></div>

<p>第二个颜色修改对应的等级是一个自定义的日志级别，这里假设用户自定义了一个<code>logging.SPAM</code>的日志级别。自定义日志级别的设置是属于python内置模块logging的内容，本质上也就是一个数值，然后可以给数值指定一个常量名称。  </p>
<h3 id="dui-stdoutstderrstdin-mo-ren-de-bu-huo-xing-wei"><a class="toclink" href="#dui-stdoutstderrstdin-mo-ren-de-bu-huo-xing-wei">对 stdout/stderr/stdin 默认的捕获行为</a></h3>
<p>上面主要是日志的内容，这里开始是输出的内容。<br>
测试期间会捕获标准输出和标准错误，测试失败的时候会显示捕获的输出。<br>
标准输入被设置为"null"对象，在尝试读取时就会失败。这么做是因为在测试时很少需要输入来交互。<br>
默认情况下，捕获是通过拦截对文件描述符的写入来实现的。可以禁用捕获，或者使用别的捕获方式。  </p>
<h4 id="bu-huo-de-she-zhi"><a class="toclink" href="#bu-huo-de-she-zhi">捕获的设置</a></h4>
<p>pytest三种捕获的方式：</p>
<ol>
<li>文件描述符（默认）：所有对系统的文件描述符1和2的写入，会被捕获</li>
<li>系统级别捕获：仅对python的<code>sys.stdout</code>和<code>sys.stderr</code>进行捕获，而不是对文件描述符的写入进行捕获</li>
<li>tee-sys捕获：对python的<code>sys.stdout</code>和<code>sys.stderr</code>进行捕获，并且会继续传递给系统，参考tee命令的作用。这样在捕获一份的同时，还能实时打印输出</li>
</ol>
<p>使用下面的命令行参数设置捕获机制：</p>
<div class="highlight"><pre><span></span><code>pytest -s                  <span class="c1"># disable all capturing</span>
pytest --capture<span class="o">=</span>sys       <span class="c1"># replace sys.stdout/stderr with in-mem files</span>
pytest --capture<span class="o">=</span>fd        <span class="c1"># also point filedescriptors 1 and 2 to temp file</span>
pytest --capture<span class="o">=</span>tee-sys   <span class="c1"># combines &#39;sys&#39; and &#39;-s&#39;, capturing sys.stdout/stderr</span>
                           <span class="c1"># and passing it along to the actual sys.stdout/stderr</span>
</code></pre></div>

<h3 id="shi-yong-print-jin-xing-debug"><a class="toclink" href="#shi-yong-print-jin-xing-debug">使用print进行debug</a></h3>
<p>默认捕获方式的一个好处是，可以简单的使用print进行debug。<br>
下面的示例，运行测试后仅会将失败的函数输出，测试成功的函数会隐藏：</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">setup_function</span><span class="p">(</span><span class="n">function</span><span class="p">):</span>
    <span class="sd">&quot;&quot;&quot;</span>
<span class="sd">    xUnit style setup</span>
<span class="sd">    当前模块下所有的测试都会自动调用这个固件</span>
<span class="sd">    &quot;&quot;&quot;</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;setting up&quot;</span><span class="p">,</span> <span class="n">function</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">test_func1</span><span class="p">():</span>
    <span class="k">assert</span> <span class="kc">True</span>

<span class="k">def</span> <span class="nf">test_func2</span><span class="p">():</span>
    <span class="k">assert</span> <span class="kc">False</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="kn">import</span> <span class="nn">pytest</span>
    <span class="n">pytest</span><span class="o">.</span><span class="n">main</span><span class="p">([</span><span class="vm">__file__</span><span class="p">])</span>

<span class="sd">&#39;&#39;&#39;</span>
<span class="sd">------------------------ Captured stdout setup ------------------------- </span>
<span class="sd">setting up &lt;function test_func2 at 0x000001B968232B80&gt;</span>
<span class="sd">======================= short test summary info ======================== </span>
<span class="sd">FAILED tests/test_module.py::test_func2</span>
<span class="sd">===================== 1 failed, 1 passed in 0.05s ======================</span>
<span class="sd">&#39;&#39;&#39;</span>
</code></pre></div>

<h3 id="duan-yan-bu-huo-de-shu-chu"><a class="toclink" href="#duan-yan-bu-huo-de-shu-chu">断言捕获的输出</a></h3>
<p>可以使用capsys、capsysbinary、capfd、capfdbinary，捕获输出。<br>
下面的示例可以测试stdout和stderr的输出：</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">test_myoutput</span><span class="p">(</span><span class="n">capsys</span><span class="p">):</span>  <span class="c1"># or use &quot;capfd&quot; for fd-level</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;hello&quot;</span><span class="p">)</span>
    <span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s2">&quot;world</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
    <span class="c1"># print(&quot;world&quot;, file=sys.stderr)  # 这句和上面等效</span>
    <span class="n">captured</span> <span class="o">=</span> <span class="n">capsys</span><span class="o">.</span><span class="n">readouterr</span><span class="p">()</span>
    <span class="k">assert</span> <span class="n">captured</span><span class="o">.</span><span class="n">out</span> <span class="o">==</span> <span class="s2">&quot;hello</span><span class="se">\n</span><span class="s2">&quot;</span>
    <span class="k">assert</span> <span class="n">captured</span><span class="o">.</span><span class="n">err</span> <span class="o">==</span> <span class="s2">&quot;world</span><span class="se">\n</span><span class="s2">&quot;</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;next&quot;</span><span class="p">)</span>
    <span class="n">captured</span> <span class="o">=</span> <span class="n">capsys</span><span class="o">.</span><span class="n">readouterr</span><span class="p">()</span>
    <span class="k">assert</span> <span class="n">captured</span><span class="o">.</span><span class="n">out</span> <span class="o">==</span> <span class="s2">&quot;next</span><span class="se">\n</span><span class="s2">&quot;</span>
</code></pre></div>

<p>这里用了capsys，这个是sys级别的捕获，也可以用capfd做文件描述符的捕获。这里替换使用capfd效果一样。<br>
而如果使用capsysbinary、capfdbinary捕获的是bytes类型。  </p>
<p>示例中调用了<code>readouterr()</code>方法，是读取到当我位置为止捕获的内容，被读取的内容会被清空。之后的输出还会继续被捕获，示例中再次调用<code>readouterr()</code>方法读取到新内容。  </p>
<p>如果使用capfd做文件描述符的捕获，提供的接口和capsys是一样的。使用capfd可以捕获到库或者子进程中输出的内容，这个没例子。  </p>
<p><code>readouterr()</code>方法的返回值是一个具名元组，分别是标准输出和标准错误：</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">sys</span>

<span class="k">def</span> <span class="nf">test_output</span><span class="p">(</span><span class="n">capsys</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;hello&quot;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;hello2&quot;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;world&quot;</span><span class="p">,</span> <span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;world2&quot;</span><span class="p">,</span> <span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span>
    <span class="n">captured</span> <span class="o">=</span> <span class="n">capsys</span><span class="o">.</span><span class="n">readouterr</span><span class="p">()</span>
    <span class="c1"># 下面两句print也是会被捕获的，不过之后并没有去读</span>
    <span class="nb">print</span><span class="p">(</span><span class="nb">type</span><span class="p">(</span><span class="n">captured</span><span class="p">))</span>
    <span class="nb">print</span><span class="p">(</span><span class="n">captured</span><span class="p">)</span>
    <span class="k">assert</span> <span class="mi">0</span>

<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">&#39;__main__&#39;</span><span class="p">:</span>
    <span class="kn">import</span> <span class="nn">pytest</span>
    <span class="n">pytest</span><span class="o">.</span><span class="n">main</span><span class="p">([</span><span class="vm">__file__</span><span class="p">])</span>

<span class="sd">&#39;&#39;&#39;</span>
<span class="sd">--------------------- Captured stdout call ---------------------- </span>
<span class="sd">&lt;class &#39;_pytest.capture.CaptureResult&#39;&gt;</span>
<span class="sd">CaptureResult(out=&#39;hello\nhello2\n&#39;, err=&#39;world\nworld2\n&#39;)       </span>
<span class="sd">==================== short test summary info ==================== </span>
<span class="sd">FAILED tests/test_readouterr.py::test_output</span>
<span class="sd">======================= 1 failed in 0.05s ======================= </span>
<span class="sd">&#39;&#39;&#39;</span>
</code></pre></div>

<p>这里看到返回的类型是一个内置的类型，不过官方说是个具名元组（namedtuple），那就当做具名元组使用好了。  </p>
<p>对于非文本数据的输出捕获，可以使用capsysbinary、capfdbinary，捕获bytes类型的输出。  </p>
<p>可以在with上下文管理器中，临时禁用捕获。有一个<code>disabled()</code>方法来禁用：</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">test_disabling_capturing</span><span class="p">(</span><span class="n">capsys</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;this output is captured&quot;</span><span class="p">)</span>
    <span class="k">with</span> <span class="n">capsys</span><span class="o">.</span><span class="n">disabled</span><span class="p">():</span>
        <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;output not captured, going directly to sys.stdout&quot;</span><span class="p">)</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;this output is also captured&quot;</span><span class="p">)</span>
</code></pre></div>
      </div>
      <div class="back-to-top">
        <a href="/">HOME</a>
        <a href="#top">TOP</a>
      </div>
    </article>
  </div>
<!-- end article -->
<!-- 页面往下滚动一段之后才会显示TOC -->
<script>
  window.onscroll = function() {
    var tocbox = document.getElementsByClassName('toc')[0];
    var osTop = document.documentElement.scrollTop || document.body.scrollTop;
    var osWidth = document.documentElement.scrollWidth || document.body.scrollWidth;
    // console.log(osTop)
    if (osTop>300 && osWidth>865) {
      tocbox.style.display = "block"
    }
    if (osTop<300 || osWidth<865) {
      tocbox.style.display = "none"
    }
  }
</script>
                <footer>
                    <div class="icons">
                    </div>
                    <span id="busuanzi_container_page_pv" style="padding: 10px">本文阅读量<span id="busuanzi_value_page_pv"></span>次</span>
                    <span id="busuanzi_container_site_pv" style="padding: 10px">本站总访问量<span id="busuanzi_value_site_pv"></span>次</span>
                    <span id="busuanzi_container_site_uv" style="padding: 10px">本站总访客数<span id="busuanzi_value_site_uv"></span>人</span>
                    <p>© <script>document.write(moment().format('YYYY'));</script> 749B</p>
                </footer>
        </div>
</body>
</html>